package cz.ctu.guiproject.server.manager;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import cz.ctu.guiproject.server.dispatcher.Dispatcher;
import cz.ctu.guiproject.server.messaging.DisconnectRequest;
import cz.ctu.guiproject.server.messaging.DisconnectResponse;
import cz.ctu.guiproject.server.messaging.InitRequest;
import cz.ctu.guiproject.server.messaging.InitResponse;
import cz.ctu.guiproject.server.messaging.ResponseState;
import cz.ctu.guiproject.server.resources.Constants;

public class ConnectionManager {

	private static final Logger logger = Logger.getLogger(ConnectionManager.class.getName());
	private final Dispatcher dispatcher;
	private final JARManager jarManager;

	public ConnectionManager(Dispatcher dispatcher) {
		this.dispatcher = dispatcher;
		jarManager = JARManager.getInstance();
		logger.info("Initializing ConnectionManager");
	}

	public void initClient(String xml) {
		final InitRequest initRequest = decodeInitRequest(xml);
		// TODO custom mechanism, that accepts/rejects new connections
		final InitResponse initResponse = new InitResponse();
		// list all available plugins and send missing ones to client
		initResponse.setResponse(ResponseState.ACCEPTED);
		initResponse.setJarUrls(jarManager.getMissingJARs(initRequest));

		final String initResponseXML = encodeInitResponse(initResponse);
		dispatcher.send(initRequest.getNetworkId(), initResponseXML);
	}

	private String encodeInitResponse(InitResponse initResponse) {
		final StringWriter writer = new StringWriter();
		try {
			final XMLStreamWriter out = XMLOutputFactory.newInstance()
					.createXMLStreamWriter(writer);

			out.writeStartDocument(Constants.CHARSET_UTF_8, Constants.VERSION_1_0);
			final ResponseState response = initResponse.getResponse();
			if (response != null) {
				out.writeStartElement(Constants.CLIENT_INIT_RESPONSE);
				out.writeStartElement(Constants.ACCEPTED_STATE);
				out.writeCharacters(response.getResponseCode());
				out.writeEndElement();

				out.writeStartElement(Constants.REQUIRED_JARS);
				final String[] arr = initResponse.getJarUrls();
				final StringBuilder sb = new StringBuilder();
				for (int i = 0; i < arr.length; i += 2) {
					if (i == 0) {
						sb.append(arr[i]);
						sb.append(", " + arr[i + 1]);
					} else {
						sb.append(", " + arr[i]);
						sb.append(", " + arr[i + 1]);
					}
				}
				out.writeCharacters(sb.toString());
				out.writeEndElement();
				out.writeEndElement();
			}
			out.writeEndDocument();
			out.close();

		} catch (XMLStreamException | FactoryConfigurationError e) {
			logger.severe(e.getMessage());
		}
		final StringBuffer sb = writer.getBuffer();
		return sb.toString();
	}

	private InitRequest decodeInitRequest(String xml) {
		// TODO get rid of [...] at the beginning
		final String nr = xml.substring(1, xml.indexOf(']'));
		final int networkId = Integer.parseInt(nr);
		xml = xml.substring(xml.indexOf(']') + 1);

		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final InitRequestHandler handler = new InitRequestHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException | SAXException | IOException e) {
			logger.severe(e.getMessage());
		}
		final InitRequest initRequest = handler.getInitRequest();
		initRequest.setNetworkId(networkId);
		return initRequest;
	}

	/**
	 * Parses init-request message and creates message object
	 */
	private class InitRequestHandler extends DefaultHandler {

		private boolean inDeviceName = false;
		private boolean inInstalledComponents = false;

		private InitRequest initRequest;

		InitRequestHandler() {
			logger.info("InitRequestHandler called!");
			initRequest = new InitRequest();
		}

		InitRequest getInitRequest() {
			return initRequest;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes)
				throws SAXException {
			inDeviceName = (qName.equals(Constants.DEVICE_NAME)) ? true : false;
			inInstalledComponents = (qName.equals(Constants.INSTALLED_COMPONENTS)) ? true : false;
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
			String chars = new String(ch).substring(start, start + length);
			if (inDeviceName) {
				initRequest.setDeviceName(chars);
			} else if (inInstalledComponents) {
				final StringTokenizer st = new StringTokenizer(chars, ",");
				final List<String> list = new LinkedList<>();
				while (st.hasMoreTokens()) {
					list.add(st.nextToken().trim());
				}
				final String[] res = new String[list.size()];
				list.toArray(res);
				initRequest.setInstalledComponents(res);
			}
		}
	}

	public void disconnectClient(String xml) {
		final DisconnectRequest disconnectRequest = decodeDisconnectRequest(xml);
		
		final DisconnectResponse disconnectResponse = new DisconnectResponse();
		disconnectResponse.setResponse(ResponseState.ACCEPTED);
		
		final String disconnectResponseXML = encodeDisconnectResponse(disconnectResponse);
		dispatcher.send(disconnectRequest.getNetworkId(), disconnectResponseXML);
	}
	
	private String encodeDisconnectResponse(DisconnectResponse disconnectResponse) {
		final StringWriter writer = new StringWriter();
		try {
			final XMLStreamWriter out = XMLOutputFactory.newInstance()
					.createXMLStreamWriter(writer);

			out.writeStartDocument(Constants.CHARSET_UTF_8, Constants.VERSION_1_0);
			final ResponseState response = disconnectResponse.getResponse();
			if (response != null) {
				out.writeStartElement(Constants.CLIENT_DISCONNECT_RESPONSE);
				out.writeStartElement(Constants.ACCEPTED_STATE);
				out.writeCharacters(response.getResponseCode());
				out.writeEndElement();
				out.writeEndElement();
			}
			out.writeEndDocument();
			out.close();

		} catch (XMLStreamException | FactoryConfigurationError e) {
			logger.severe(e.getMessage());
		}
		final StringBuffer sb = writer.getBuffer();
		return sb.toString();
	}

	private DisconnectRequest decodeDisconnectRequest(String xml) {
		// TODO get rid of [...] at the beginning
		final String nr = xml.substring(1, xml.indexOf(']'));
		final int networkId = Integer.parseInt(nr);
		xml = xml.substring(xml.indexOf(']') + 1);

		final SAXParserFactory factory = SAXParserFactory.newInstance();
		final DisconnectRequestHandler handler = new DisconnectRequestHandler();
		try {
			final SAXParser saxParser = factory.newSAXParser();
			saxParser.parse(new InputSource(new StringReader(xml)), handler);
		} catch (ParserConfigurationException | SAXException | IOException e) {
			logger.severe(e.getMessage());
		}
		final DisconnectRequest disconnectRequest = handler.getDisconnectRequest();
		disconnectRequest.setNetworkId(networkId);
		return disconnectRequest;
	}

	/**
	 * Parses disconnect-request message and creates message object
	 */
	private class DisconnectRequestHandler extends DefaultHandler {

		private DisconnectRequest disconnectRequest;

		DisconnectRequestHandler() {
			logger.info("DisconnectRequestHandler called.");
			disconnectRequest = new DisconnectRequest();
		}
		
		DisconnectRequest getDisconnectRequest() {
			return disconnectRequest;
		}

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes)
				throws SAXException {
		}

		@Override
		public void characters(char[] ch, int start, int length) throws SAXException {
//			String chars = new String(ch).substring(start, start + length);
		}
	}
}
